SPORE.$Import('core.element.style');

/*
---

name: Element.Dimensions

description: Contains methods to work with size, scroll, or positioning of Elements and the window object.

license: MIT-style license.

credits:
  - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
  - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).

requires: [Element, Element.Style]

provides: [Element.Dimensions]

...
*/

SPORE.register('core.element.dimensions',function($ns){
	
	var element = document.createElement('div'),
		child = document.createElement('div');
	element.style.height = '0';
	element.appendChild(child);
	var brokenOffsetParent = (child.offsetParent === element);
	element = child = null;
	
	var isOffset = function(el){
		return styleString(el, 'position') != 'static' || isBody(el);
	};
	
	var isOffsetStatic = function(el){
		return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
	};
	
	Element.implement({
	
		scrollTo: function(x, y){
			if (isBody(this)){
				this.getWindow().scrollTo(x, y);
			} else {
				this.scrollLeft = x;
				this.scrollTop = y;
			}
			return this;
		},
	
		getSize: function(){
			if (isBody(this)) return this.getWindow().getSize();
			return {x: this.offsetWidth, y: this.offsetHeight};
		},
	
		getScrollSize: function(){
			if (isBody(this)) return this.getWindow().getScrollSize();
			return {x: this.scrollWidth, y: this.scrollHeight};
		},
	
		getScroll: function(){
			if (isBody(this)) return this.getWindow().getScroll();
			return {x: this.scrollLeft, y: this.scrollTop};
		},
	
		getScrolls: function(){
			var element = this.parentNode, position = {x: 0, y: 0};
			while (element && !isBody(element)){
				position.x += element.scrollLeft;
				position.y += element.scrollTop;
				element = element.parentNode;
			}
			return position;
		},
	
		getOffsetParent: brokenOffsetParent ? function(){
			var element = this;
			if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
	
			var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
			while ((element = element.parentNode)){
				if (isOffsetCheck(element)) return element;
			}
			return null;
		} : function(){
			var element = this;
			if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
	
			try {
				return element.offsetParent;
			} catch(e) {}
			return null;
		},
	
		getOffsets: function(){
			if (this.getBoundingClientRect && !Browser.Platform.ios){
				var bound = this.getBoundingClientRect(),
					html = document.id(this.getDocument().documentElement),
					htmlScroll = html.getScroll(),
					elemScrolls = this.getScrolls(),
					isFixed = (styleString(this, 'position') == 'fixed');
	
				return {
					x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
					y: bound.top.toInt()  + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
				};
			}
	
			var element = this, position = {x: 0, y: 0};
			if (isBody(this)) return position;
	
			while (element && !isBody(element)){
				position.x += element.offsetLeft;
				position.y += element.offsetTop;
	
				if (Browser.firefox){
					if (!borderBox(element)){
						position.x += leftBorder(element);
						position.y += topBorder(element);
					}
					var parent = element.parentNode;
					if (parent && styleString(parent, 'overflow') != 'visible'){
						position.x += leftBorder(parent);
						position.y += topBorder(parent);
					}
				} else if (element != this && Browser.safari){
					position.x += leftBorder(element);
					position.y += topBorder(element);
				}
	
				element = element.offsetParent;
			}
			if (Browser.firefox && !borderBox(this)){
				position.x -= leftBorder(this);
				position.y -= topBorder(this);
			}
			return position;
		},
	
		getPosition: function(relative){
			if (isBody(this)) return {x: 0, y: 0};
			var offset = this.getOffsets(),
				scroll = this.getScrolls();
			var position = {
				x: offset.x - scroll.x,
				y: offset.y - scroll.y
			};
			
			if (relative && (relative = document.id(relative))){
				var relativePosition = relative.getPosition();
				return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
			}
			return position;
		},
	
		getCoordinates: function(element){
			if (isBody(this)) return this.getWindow().getCoordinates();
			var position = this.getPosition(element),
				size = this.getSize();
			var obj = {
				left: position.x,
				top: position.y,
				width: size.x,
				height: size.y
			};
			obj.right = obj.left + obj.width;
			obj.bottom = obj.top + obj.height;
			return obj;
		},
	
		computePosition: function(obj){
			return {
				left: obj.x - styleNumber(this, 'margin-left'),
				top: obj.y - styleNumber(this, 'margin-top')
			};
		},
	
		setPosition: function(obj){
			return this.setStyles(this.computePosition(obj));
		}
	
	});
	
	
	[Document, Window].invoke('implement', {
	
		getSize: function(){
			var doc = getCompatElement(this);
			return {x: doc.clientWidth, y: doc.clientHeight};
		},
	
		getScroll: function(){
			var win = this.getWindow(), doc = getCompatElement(this);
			return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
		},
	
		getScrollSize: function(){
			var doc = getCompatElement(this),
				min = this.getSize(),
				body = this.getDocument().body;
	
			return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
		},
	
		getPosition: function(){
			return {x: 0, y: 0};
		},
	
		getCoordinates: function(){
			var size = this.getSize();
			return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
		}
	
	});
	
	// private methods
	
	var styleString = Element.getComputedStyle;
	
	function styleNumber(element, style){
		return styleString(element, style).toInt() || 0;
	}
	
	function borderBox(element){
		return styleString(element, '-moz-box-sizing') == 'border-box';
	}
	
	function topBorder(element){
		return styleNumber(element, 'border-top-width');
	}
	
	function leftBorder(element){
		return styleNumber(element, 'border-left-width');
	}
	
	function isBody(element){
		return (/^(?:body|html)$/i).test(element.tagName);
	}
	
	function getCompatElement(element){
		var doc = element.getDocument();
		return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
	}

	//aliases
	Element.alias({position: 'setPosition'}); //compatability
	
	[Window, Document, Element].invoke('implement', {
	
		getHeight: function(){
			return this.getSize().y;
		},
	
		getWidth: function(){
			return this.getSize().x;
		},
	
		getScrollTop: function(){
			return this.getScroll().y;
		},
	
		getScrollLeft: function(){
			return this.getScroll().x;
		},
	
		getScrollHeight: function(){
			return this.getScrollSize().y;
		},
	
		getScrollWidth: function(){
			return this.getScrollSize().x;
		},
	
		getTop: function(){
			return this.getPosition().y;
		},
	
		getLeft: function(){
			return this.getPosition().x;
		}
	
	});

});